import atexit
import json
import os
import mysql.connector
from flask import Flask, render_template, request, jsonify
from apscheduler.schedulers.background import BackgroundScheduler
from collections import defaultdict
from threading import RLock
from datetime import datetime, date, timedelta
import logging
import sys
import traceback
#测试gitee11
# 配置日志
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
app = Flask(__name__)


# 全局状态管理
class AppState:
    __slots__ = ('persons', 'lock', 'scheduler', 'initialized')

    def __init__(self):
        self.persons = []  # 人员数据
        self.lock = RLock()  # 可重入锁
        self.scheduler = BackgroundScheduler(daemon=True)
        self.initialized = False  # 初始化标志


app_state = AppState()

# MySQL配置
MYSQL_CONFIG = {
    "host": "106.54.18.228",  #远程腾讯云服务器IP：106.54.18.228
    "user": "admin",
    "password": "831114399x",
    "database": "person_db",
    "port": 3306
}

# 自定义日期序列化器
class DateEncoder(json.JSONEncoder):
    """处理日期对象的JSON序列化问题"""

    def default(self, obj):
        if isinstance(obj, (date, datetime)):
            return obj.strftime('%Y-%m-%d')
        return json.JSONEncoder.default(self, obj)


def fetch_mysql_data():
    """从MySQL获取数据并处理日期序列化问题"""
    conn = None
    cursor = None
    try:
        conn = mysql.connector.connect(**MYSQL_CONFIG)
        cursor = conn.cursor(dictionary=True)
        cursor.execute("SELECT id, room_number, name, join_date FROM persons")
        persons_data = cursor.fetchall()

        # 原子文件写入
        tmp_path = 'person_data.tmp'
        with open(tmp_path, 'w', encoding='utf-8') as f:
            json.dump(persons_data, f, ensure_ascii=False, indent=2, cls=DateEncoder)
        return persons_data
    except mysql.connector.Error as db_err:
        app.logger.error(f"数据库错误: {str(db_err)}")
        return None
    except Exception as e:
        app.logger.error(f"数据获取异常: {str(e)}")
        return None
    finally:
        if cursor: cursor.close()
        if conn and conn.is_connected(): conn.close()


def update_persons():
    """带重试机制的数据更新"""
    with app_state.lock:
        new_data = fetch_mysql_data()
        if new_data is not None:
            app_state.persons = new_data
            app.logger.info("内存数据更新成功")
        else:
            try:
                if os.path.exists('person_data.tmp'):
                    encodings = ['utf-8', 'latin-1', 'iso-8859-1', 'cp1252']
                    for enc in encodings:
                        try:
                            with open('person_data.tmp', 'r', encoding=enc) as f:
                                app_state.persons = json.load(f)
                            app.logger.warning(f"使用{enc}编码成功回退到本地缓存")
                            return
                        except UnicodeDecodeError:
                            continue
                    raise ValueError("无法确定缓存文件编码")
            except Exception as e:
                app.logger.error(f"缓存加载失败: {str(e)}")
                app_state.persons = []


def generate_schedule(room_persons, base_date=None):
    """生成房间的排班表 - 支持跨周连续排班"""
    if not room_persons:
        return []

    # 按入所日期排序
    sorted_persons = sorted(room_persons, key=lambda x: x['join_date'])
    total_persons = len(sorted_persons)

    # 使用传入日期或当前日期
    today = base_date if base_date else datetime.now().date()

    # 计算轮次偏移量 - 基于累计周数
    start_date = date(2023, 1, 1)  # 系统起始日期
    weeks_since_start = (today - start_date).days // 7

    # 初始化7天排班表
    schedule = []
    shifts = ['午班', '夜班1', '夜班2', '夜班3']

    # 计算本周一的日期
    days_to_monday = today.weekday()  # 0=周一, 6=周日
    monday = today - timedelta(days=days_to_monday)

    # 从周一开始生成7天的排班表
    for day_offset in range(7):
        day_date = monday + timedelta(days=day_offset)
        day_schedule = {}
        date_str = day_date.strftime('%Y-%m-%d')

        for shift_idx, shift in enumerate(shifts):
            # 计算人员索引（考虑轮转）
            start_idx = (weeks_since_start * 8 + day_offset * len(shifts) + shift_idx * 2) % total_persons
            end_idx = (start_idx + 2) % total_persons

            if start_idx < end_idx:
                persons = sorted_persons[start_idx:end_idx]
            else:
                persons = sorted_persons[start_idx:] + sorted_persons[:end_idx]

            day_schedule[shift] = persons

        schedule.append({
            'date': date_str,
            'shifts': day_schedule
        })

    return schedule


def get_schedule_by_date(room_number, week_date):
    """获取指定周的值班表"""
    conn = None
    cursor = None
    try:
        conn = mysql.connector.connect(**MYSQL_CONFIG)
        cursor = conn.cursor(dictionary=True)

        # 计算指定周的周一日期
        target_date = datetime.strptime(week_date, '%Y-%m-%d').date()
        days_to_monday = target_date.weekday()
        monday = target_date - timedelta(days=days_to_monday)

        query = """
            SELECT schedule_data, bed_data, seat_data 
            FROM schedules 
            WHERE room_number = %s 
            AND week_start_date = %s
        """
        cursor.execute(query, (room_number, monday))
        result = cursor.fetchone()

        # 确保读取所有结果
        while cursor.nextset():
            pass

        return result if result else None
    except mysql.connector.Error as db_err:
        app.logger.error(f"数据库查询错误: {str(db_err)}")
        return None
    except Exception as e:
        app.logger.error(f"获取值班表异常: {str(e)}")
        return None
    finally:
        if cursor: cursor.close()
        if conn and conn.is_connected(): conn.close()


def save_schedule_to_db(room_number, schedule_data, target_date=None):
    """将值班表保存到数据库"""
    conn = None
    cursor = None
    try:
        conn = mysql.connector.connect(**MYSQL_CONFIG)
        cursor = conn.cursor()

        # 使用传入日期或当前日期
        today = datetime.strptime(target_date, '%Y-%m-%d').date() if target_date else datetime.now().date()
        days_to_monday = today.weekday()
        monday = today - timedelta(days=days_to_monday)

        # 将排班数据转换为JSON字符串
        schedule_json = json.dumps(schedule_data, ensure_ascii=False, cls=DateEncoder)

        # 使用ON DUPLICATE KEY UPDATE处理重复数据
        query = """
            INSERT INTO schedules (room_number, week_start_date, schedule_data)
            VALUES (%s, %s, %s)
            ON DUPLICATE KEY UPDATE schedule_data = VALUES(schedule_data)
        """
        cursor.execute(query, (room_number, monday, schedule_json))
        conn.commit()

        return True
    except mysql.connector.Error as db_err:
        app.logger.error(f"数据库保存错误: {str(db_err)}")
        return False
    except Exception as e:
        app.logger.error(f"保存值班表异常: {str(e)}")
        return False
    finally:
        if cursor: cursor.close()
        if conn and conn.is_connected(): conn.close()


def save_bed_layout_to_db(room_number, bed_data, target_date=None):
    """将铺位布局保存到数据库"""
    conn = None
    cursor = None
    try:
        conn = mysql.connector.connect(**MYSQL_CONFIG)
        cursor = conn.cursor()

        # 使用传入日期或当前日期
        today = datetime.strptime(target_date, '%Y-%m-%d').date() if target_date else datetime.now().date()
        days_to_monday = today.weekday()
        monday = today - timedelta(days=days_to_monday)

        # 将数据转换为JSON字符串
        bed_json = json.dumps(bed_data, ensure_ascii=False, cls=DateEncoder)

        # 创建一个空的schedule_data JSON数组
        #schedule_json = json.dumps([], ensure_ascii=False)

        # 创建一个空的seat_data JSON数组
        #seat_json = json.dumps([], ensure_ascii=False)

        # 使用ON DUPLICATE KEY UPDATE处理重复数据
        query = """
            INSERT INTO schedules (room_number, week_start_date, bed_data)
            VALUES (%s, %s, %s)
            ON DUPLICATE KEY UPDATE 
                bed_data = VALUES(bed_data)
        """
        cursor.execute(query, (room_number, monday, bed_json))
        conn.commit()

        return True
    except mysql.connector.Error as db_err:
        app.logger.error(f"数据库保存错误: {str(db_err)}")
        return False
    except Exception as e:
        app.logger.error(f"保存铺位布局异常: {str(e)}")
        return False
    finally:
        if cursor: cursor.close()
        if conn and conn.is_connected(): conn.close()


def save_seat_layout_to_db(room_number, seat_data, target_date=None):
    """将座位布局保存到数据库"""
    conn = None
    cursor = None
    try:
        conn = mysql.connector.connect(**MYSQL_CONFIG)
        cursor = conn.cursor()

        # 使用传入日期或当前日期
        today = datetime.strptime(target_date, '%Y-%m-%d').date() if target_date else datetime.now().date()
        days_to_monday = today.weekday()
        monday = today - timedelta(days=days_to_monday)

        # 将数据转换为JSON字符串
        seat_json = json.dumps(seat_data, ensure_ascii=False, cls=DateEncoder)

        # 创建一个空的schedule_data JSON数组
        #schedule_json = json.dumps([], ensure_ascii=False)

        # 创建一个空的bed_data JSON数组
        #bed_json = json.dumps([], ensure_ascii=False)

        # 使用ON DUPLICATE KEY UPDATE处理重复数据
        query = """
            INSERT INTO schedules (room_number, week_start_date,seat_data)
            VALUES (%s, %s, %s)
            ON DUPLICATE KEY UPDATE 
                seat_data = VALUES(seat_data)
                
        """
        cursor.execute(query, (room_number, monday, seat_json))
        conn.commit()

        return True
    except mysql.connector.Error as db_err:
        app.logger.error(f"数据库保存错误: {str(db_err)}")
        return False
    except Exception as e:
        app.logger.error(f"保存座位布局异常: {str(e)}")
        return False
    finally:
        if cursor: cursor.close()
        if conn and conn.is_connected(): conn.close()


def generate_bed_layout(room_persons):
    """生成铺位布局"""
    if not room_persons:
        return []

    # 按入所日期排序
    sorted_persons = sorted(room_persons, key=lambda x: x['join_date'])

    # 生成铺位布局数据
    bed_layout = []
    for i, person in enumerate(sorted_persons):
        bed_layout.append({
            'id': person['id'],
            'name': person['name'],
            'join_date': person['join_date'],
            'position': f"铺位{i + 1}"
        })

    return bed_layout


def generate_seat_layout(room_persons):
    """生成座位布局"""
    if not room_persons:
        return []

    # 按入所日期排序
    sorted_persons = sorted(room_persons, key=lambda x: x['join_date'])

    # 生成座位布局数据
    seat_layout = []
    for i, person in enumerate(sorted_persons):
        seat_layout.append({
            'id': person['id'],
            'name': person['name'],
            'join_date': person['join_date'],
            'position': f"座位{i + 1}"
        })

    return seat_layout


def init_scheduler():
    """安全初始化调度器"""
    with app_state.lock:
        if not app_state.initialized:
            # 配置定时任务
            app_state.scheduler.add_job(
                func=update_persons,
                trigger='interval',
                minutes=10,
                max_instances=1,
                coalesce=True,
                misfire_grace_time=300
            )

            # 启动前状态检查
            if not app_state.scheduler.running:
                try:
                    app_state.scheduler.start()
                    app.logger.info("调度器启动成功")
                except Exception as e:
                    app.logger.critical(f"调度器启动失败: {str(e)}")
                    raise

            # 首次数据加载
            update_persons()
            app_state.initialized = True
            app.logger.info("应用初始化完成")


@app.before_request
def before_first_request():
    """请求前初始化检查"""
    if not app_state.initialized:
        init_scheduler()


@app.route('/')
def index():
    """首页路由 - 显示楼层和房间统计数据"""
    with app_state.lock:
        current_data = app_state.persons

    # 确保数据是列表类型
    if not isinstance(current_data, list):
        app.logger.error(f"无效的数据格式: {type(current_data)}")
        current_data = []

    room_stats = defaultdict(int)
    floor_counts = defaultdict(int)

    for p in current_data:
        room = p.get('room_number', '')
        if room:
            room_stats[room] += 1
            floor_num = room[0] if room else '0'
            floor_counts[floor_num] += 1

    # 组织楼层数据
    floors = defaultdict(list)
    for room, count in room_stats.items():
        floor_num = room[0] if room else '0'
        floors[floor_num].append({'number': room, 'count': count})

    # 排序处理
    floors = dict(sorted(floors.items()))
    for floor in floors.values():
        floor.sort(key=lambda x: x['number'])

    return render_template(
        'index.html',
        floors=floors,
        floor_counts=floor_counts,
        total_persons=len(current_data),
        total_rooms=sum(len(v) for v in floors.values())
    )


@app.route('/room/<room_number>')
def room_detail(room_number):
    """房间详情页 - 增加排班功能和日期选择"""
    with app_state.lock:
        current_data = app_state.persons
    if not isinstance(current_data, list):
        current_data = []

    filtered = []
    for p in current_data:
        try:
            if (isinstance(p, dict) and
                    str(p.get('room_number')) == room_number and
                    p.get('join_date')):
                # 统一join_date的格式处理
                join_date = p.get('join_date')
                if isinstance(join_date, (datetime, date)):
                    join_date = join_date.strftime('%Y-%m-%d')

                filtered.append({
                    'id': str(p['id'])[:8],
                    'name': p.get('name', '')[:15],
                    'join_date': join_date,
                    'room_number': p.get('room_number')
                })
        except Exception as e:
            app.logger.error(f"数据过滤错误: {str(e)}")

    sorted_data = sorted(filtered, key=lambda x: x['join_date'])

    # 获取当前周和默认显示周
    current_week = datetime.now().date()
    selected_week = request.args.get('week', current_week.strftime('%Y-%m-%d'))

    # 尝试获取当周值班表、床位数据、座位数据
    schedule_result = get_schedule_by_date(room_number, selected_week)

    # 确保正确处理可能的None值
    if schedule_result:
        schedule = schedule_result.get('schedule_data')
        beddata = schedule_result.get('bed_data')
        seatdata = schedule_result.get('seat_data')
    else:
        schedule = None
        beddata = None
        seatdata = None

    beddata_exists = beddata is not None
    seatdata_exists = seatdata is not None
    schedule_exists = schedule is not None

    # 如果没有值班表，生成一个空结构用于前端显示
    if not schedule_exists:
        # 创建空值班表结构
        schedule = []
        target_date = datetime.strptime(selected_week, '%Y-%m-%d').date()
        days_to_monday = target_date.weekday()
        monday = target_date - timedelta(days=days_to_monday)

        for i in range(7):
            current_date = monday + timedelta(days=i)
            schedule.append({
                'date': current_date.strftime('%Y-%m-%d'),
                'shifts': {
                    '午班': [],
                    '夜班1': [],
                    '夜班2': [],
                    '夜班3': []
                }
            })
    else:
        # 如果从数据库获取到数据，解析JSON
        schedule = json.loads(schedule)

    # 如果没有床位数据，生成一个空结构用于前端显示
    if not beddata_exists:
        beddata = []
    else:
        # 如果从数据库获取到数据，解析JSON
        beddata = json.loads(beddata)

    # 如果没有座位数据，生成一个空结构用于前端显示
    if not seatdata_exists:
        seatdata = []
    else:
        # 如果从数据库获取到数据，解析JSON
        seatdata = json.loads(seatdata)

    # 计算当前周的周一日期
    days_to_current_monday = current_week.weekday()
    current_week_monday = (current_week - timedelta(days=days_to_current_monday)).strftime('%Y-%m-%d')

    # 生成未来4周的日期选项
    week_options = []
    for i in range(-2, 3):  # 包括前2周和后2周
        week_date = current_week + timedelta(weeks=i)
        days_to_monday = week_date.weekday()
        monday = week_date - timedelta(days=days_to_monday)
        sunday = monday + timedelta(days=6)
        label = f"{monday.strftime('%m-%d')}至{sunday.strftime('%m-%d')}"
        week_options.append({
            'value': week_date.strftime('%Y-%m-%d'),
            'label': label,
            'is_current': i == 0
        })

    return render_template(
        'room_simulate.html',
        room_number=room_number,
        data=sorted_data,
        schedule=schedule,
        schedule_exists=schedule_exists,
        beddata=beddata,
        beddata_exists=beddata_exists,
        seatdata=seatdata,
        seatdata_exists=seatdata_exists,
        total=len(sorted_data),
        week_options=week_options,
        selected_week=selected_week,
        current_week_monday=current_week_monday,  # 添加当前周的周一日期
        backendBedData=json.dumps(beddata),  # 传递铺位数据
        backendSeatData=json.dumps(seatdata)  # 传递座位数据
    )


@app.route('/generate_schedule/<room_number>', methods=['POST'])
def generate_schedule_endpoint(room_number):
    """生成值班表API - 支持指定日期"""
    try:
        target_date = request.json.get('target_date')
        base_date = datetime.strptime(target_date, '%Y-%m-%d').date() if target_date else None

        with app_state.lock:
            current_data = app_state.persons

        # 过滤指定房间的数据
        filtered = []
        for p in current_data:
            try:
                if (isinstance(p, dict) and
                        str(p.get('room_number')) == room_number and
                        p.get('join_date')):
                    filtered.append(p)
            except Exception:
                continue

        # 按加入日期排序
        sorted_data = sorted(filtered, key=lambda x: x['join_date'])

        # 生成排班表
        schedule = generate_schedule(sorted_data, base_date)

        # 保存到数据库
        if target_date:
            save_success = save_schedule_to_db(room_number, schedule, target_date)
        else:
            save_success = save_schedule_to_db(room_number, schedule)

        return jsonify({
            'success': True,
            'schedule': schedule,
            'saved': save_success
        })
    except Exception as e:
        app.logger.error(f"生成值班表错误: {str(e)}")
        app.logger.error(traceback.format_exc())
        return jsonify({
            'success': False,
            'message': f"生成值班表失败: {str(e)}"
        }), 500


@app.route('/generate_bed_layout/<room_number>', methods=['POST'])
def generate_bed_layout_endpoint(room_number):
    """生成铺位布局API"""
    try:
        target_date = request.json.get('target_date')

        with app_state.lock:
            current_data = app_state.persons

        # 过滤指定房间的数据
        filtered = []
        for p in current_data:
            try:
                if (isinstance(p, dict) and
                        str(p.get('room_number')) == room_number and
                        p.get('join_date')):
                    filtered.append(p)
            except Exception:
                continue

        # 生成铺位布局
        bed_layout = generate_bed_layout(filtered)

        # 保存到数据库
        save_success = save_bed_layout_to_db(room_number, bed_layout, target_date)

        return jsonify({
            'success': True,
            'bed_layout': bed_layout,
            'saved': save_success
        })
    except Exception as e:
        app.logger.error(f"生成铺位布局错误: {str(e)}")
        app.logger.error(traceback.format_exc())
        return jsonify({
            'success': False,
            'message': f"生成铺位布局失败: {str(e)}"
        }), 500


@app.route('/generate_seat_layout/<room_number>', methods=['POST'])
def generate_seat_layout_endpoint(room_number):
    """生成座位布局API"""
    try:
        target_date = request.json.get('target_date')

        with app_state.lock:
            current_data = app_state.persons

        # 过滤指定房间的数据
        filtered = []
        for p in current_data:
            try:
                if (isinstance(p, dict) and
                        str(p.get('room_number')) == room_number and
                        p.get('join_date')):
                    filtered.append(p)
            except Exception:
                continue

        # 生成座位布局
        seat_layout = generate_seat_layout(filtered)

        # 保存到数据库
        save_success = save_seat_layout_to_db(room_number, seat_layout, target_date)

        return jsonify({
            'success': True,
            'seat_layout': seat_layout,
            'saved': save_success
        })
    except Exception as e:
        app.logger.error(f"生成座位布局错误: {str(e)}")
        app.logger.error(traceback.format_exc())
        return jsonify({
            'success': False,
            'message': f"生成座位布局失败: {str(e)}"
        }), 500


@app.route('/save_schedule/<room_number>', methods=['POST'])
def save_schedule_endpoint(room_number):
    """保存值班表到数据库API"""
    try:
        schedule_data = request.json.get('schedule')
        target_date = request.json.get('target_date')
        if not schedule_data:
            return jsonify({'success': False, 'message': '缺少排班数据'}), 400

        success = save_schedule_to_db(room_number, schedule_data, target_date)

        return jsonify({
            'success': success,
            'message': '保存成功' if success else '保存失败'
        })
    except Exception as e:
        app.logger.error(f"保存值班表错误: {str(e)}")
        app.logger.error(traceback.format_exc())
        return jsonify({
            'success': False,
            'message': f"保存值班表失败: {str(e)}"
        }), 500


@app.route('/save_bed_layout/<room_number>', methods=['POST'])
def save_bed_layout_endpoint(room_number):
    """保存铺位布局到数据库API"""
    try:
        data = request.json
        bed_data = data.get('bed_data')
        target_date = data.get('target_date')

        if bed_data is None:
            return jsonify({'success': False, 'message': '缺少铺位数据'}), 400

        success = save_bed_layout_to_db(room_number, bed_data, target_date)

        return jsonify({
            'success': success,
            'message': '保存成功' if success else '保存失败'
        })
    except Exception as e:
        app.logger.error(f"保存铺位布局错误: {str(e)}")
        app.logger.error(traceback.format_exc())
        return jsonify({
            'success': False,
            'message': f"保存铺位布局失败: {str(e)}"
        }), 500


@app.route('/save_seat_layout/<room_number>', methods=['POST'])
def save_seat_layout_endpoint(room_number):
    """保存座位布局到数据库API"""
    try:
        data = request.json
        seat_data = data.get('seat_data')
        target_date = data.get('target_date')

        if seat_data is None:
            return jsonify({'success': False, 'message': '缺少座位数据'}), 400

        success = save_seat_layout_to_db(room_number, seat_data, target_date)

        return jsonify({
            'success': success,
            'message': '保存成功' if success else '保存失败'
        })
    except Exception as e:
        app.logger.error(f"保存座位布局错误: {str(e)}")
        app.logger.error(traceback.format_exc())
        return jsonify({
            'success': False,
            'message': f"保存座位布局失败: {str(e)}"
        }), 500


def shutdown_scheduler():
    """安全关闭例程"""
    with app_state.lock:
        if app_state.scheduler.running:
            app_state.scheduler.shutdown(wait=False)
            app.logger.info("调度器已关闭")


atexit.register(shutdown_scheduler)

if __name__ == '__main__':
    app.logger.handlers.clear()
    app.logger.addHandler(logging.StreamHandler(sys.stdout))
    app.logger.setLevel(logging.INFO)

    init_scheduler()
    try:
        app.run(
            host='0.0.0.0',
            port=5000,
            debug=False,
            use_reloader=False
        )
    except Exception as e:
        app.logger.critical(f"应用启动失败: {str(e)}")
    finally:
        shutdown_scheduler()
